﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Threading;

namespace IGolovkine.Core.Testing
{
    public static class Tester
    {
        public static string GenerateUniqueString()
        {
            return Guid.NewGuid().ToString();
        }

        public static byte[] GenerateUniqueData()
        {
            return Guid.NewGuid().ToByteArray();
        }

        public static void WaitFor(Expression<Func<bool>> condition, TimeSpan timeOut)
        {
            WaitWhileEqual(condition, false, timeOut);
        }

        public static void WaitWhile(Expression<Func<bool>> condition, TimeSpan timeOut)
        {
            WaitWhileEqual(condition, true, timeOut);
        }

        public static void WaitWhileEqual<T>(Expression<Func<T>> function, T result, TimeSpan timeOut)
        {
            Func<T> compiledFunction = function.Compile();

            DateTime start = DateTime.Now;
            while (object.Equals(compiledFunction(), result) && (DateTime.Now - start) < timeOut)
            {
                Thread.Sleep(1);
            }
            if (object.Equals(compiledFunction(), result))
            {
                Fail(string.Format("{0} has timed out", function));
            }
            Thread.Sleep(10);
        }

        public static void ExecuteIn(Expression<Action> action, TimeSpan timeOut)
        {
            Thread threadToKill = null;
            Action wrappedAction = () =>
            {
                threadToKill = Thread.CurrentThread;
                action.Compile()();
            };

            IAsyncResult result = wrappedAction.BeginInvoke(null, null);
            if (result.AsyncWaitHandle.WaitOne(timeOut))
            {
                wrappedAction.EndInvoke(result);
            }
            else
            {
                threadToKill.Abort();
                Fail("{0} didn't return within {1}", action, timeOut);
            }
        }
       
        public static void AssertContents<T>(Expression<Func<IEnumerable<T>>> listFunction, params T [] values)
        {
            Func<IEnumerable<T>> compiledListFunction = listFunction.Compile();
            IEnumerable<T> list = compiledListFunction();
            T[] array = list.ToArray();
            if (array.Length != values.Length)
            {
                Fail("Number of items in {0} doesn't match {1}", listFunction, values.Length);
            }
            else
            {
                for (int i = 0; i < array.Length; i++)
                {
                    if (object.Equals(array[i], values[i]) == false)
                    {
                        Fail("Item {0} of {1} doesn't equal to {2}", array[i], listFunction, values[i]);
                    }
                }
            }
        }
        
        public static void Fail(string format, params object [] args)
        {
            throw new TesterException(string.Format(format, args));
        }

        
        public static void Assert(params Expression<Func<bool>> [] conditions)
        {
            foreach (var condition in conditions)
            {
                Func<bool> compiledCondition = condition.Compile();
                if (compiledCondition() == false)
                {
                    Fail("{0} is false", condition);
                }
            }
        }

        public static void AssertEmpty<T>(Expression<Func<IEnumerable<T>>> listFunction)
        {
            Func<IEnumerable<T>> compiledFunction = listFunction.Compile();
            if (compiledFunction().Count() != 0)
            {
                Tester.Fail("{0} is not empty", listFunction);
            }
        }
        
        public static void AssertContents<T>(Expression<Func<IEnumerable<T>>> listFunction, params Expression<Func<T, bool>>[] conditions)
        {
            Func<IEnumerable<T>> compiledFunction = listFunction.Compile();
            T[] array = compiledFunction().ToArray();
            if (array.Length != conditions.Length)
            {
                Fail("Number of items ({0}) in {1} doesn't match to number of conditions ({2})", array.Length, listFunction, conditions.Length);
            }

            for (int i = 0; i < conditions.Length; i++)
            {
                Func<T, bool> compiledCondition = conditions[i].Compile();
                if (compiledCondition(array[i]) == false)
                {
                    Fail("Item {0} of the list doesn't satisfy {1}", array[i], conditions); 
                }
            }
        }

        public static void ExpectException<E>(Action action) where E : Exception
        {
            ExpectException<E>(action, null);
        }

        public static void ExpectException<E>(Action action, Action<E> callBack) where E : Exception
        {
            bool fail = false;
            try
            {
                action();
                fail = true;
            }
            catch (Exception ex)
            {
                if ((ex is E) == false)
                {
                    Fail("Expected expection of type {0}, instead an exception of type {1} was thrown", typeof(E), ex.GetType());
                }
                else
                {

                    if (callBack != null)
                    {
                        callBack((E) ex);
                    }
                }
            }
            if (fail)
            {
                Fail("Expected expection of type {0}, no exception was thrown", typeof(E));
            }
        }
    }
}
